﻿/*
 * PeerIDUntility
 * 
 * PeerID Generate and analysis alogrithm is based on
 * Peer ID Conventions Definition
 * See http://www.bittorrent.org/beps/bep_0020.html
 * 
 * Using this format
 * 
 * -XX0000-XXXXXXXXXXXX.....
 * 
 * Author: snowdreamist
 * Date: 2011/11/20
 * 
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace BitTorrentSharp.Torrent
{
    public static class PeerIDUtility
    {
        /// <summary>
        /// PeerID prefix
        /// 
        /// BP represents BitTorrentSharp
        /// </summary>
        private static readonly byte[] _peerIDPrefix = new byte[8]
        {
            (byte)'-',
            (byte)'B', 
            (byte)'P',
            GetVersionChar(Assembly.GetExecutingAssembly().GetName().Version.Major),
            GetVersionChar(Assembly.GetExecutingAssembly().GetName().Version.Minor),
            GetVersionChar(Assembly.GetExecutingAssembly().GetName().Version.Build),
            GetVersionChar(Assembly.GetExecutingAssembly().GetName().Version.Revision),
            (byte)'-'
        };
        /// <summary>
        /// verify a peerID
        /// </summary>
        /// <param name="peerID"></param>
        /// <returns></returns>
        public static bool VerifyPeerID(string peerID)
        {
            if (peerID == null)
                return false;
            // check in bytes or not?
            return Encoding.Default.GetByteCount(peerID) == 20;
        }
        /// <summary>
        /// Generate new peer id
        /// </summary>
        /// <returns></returns>
        public static byte[] GeneratePeerID()
        {
            byte[] randomChars = new byte[12];
            // generate random chars
            Random random = new Random();

            for (int i = 0; i < 12; ++i)
                randomChars[i] = (byte)random.Next(byte.MinValue, byte.MaxValue);

            return _peerIDPrefix.Concat(randomChars).ToArray();
        }
        /// <summary>
        /// parse a peer id
        /// </summary>
        /// <param name="peerID"></param>
        public static bool TryParsePeerID(byte[] peerID, out string shortName, out Version version)
        {
            shortName = default(string);
            version = default(Version);

            // check parameter
            if (peerID == null || peerID.Length != 20)
                return false;

            if (peerID[0] == '-' && peerID[7] == '-')
            {
                char[] shortNameChars = new char[]
                {
                    (char)peerID[1],
                    (char)peerID[2]
                };

                shortName = new string(shortNameChars);

                int[] versionNumbers = new int[4] { 0, 0, 0, 0 };

                for (int i = 0; i < 4; ++i)
                    if (peerID[i + 3] != '-')
                        versionNumbers[i] = GetVersionNumber(peerID[i + 3]);

                version = new Version(versionNumbers[0], versionNumbers[1], versionNumbers[2], versionNumbers[3]);

                return true;
            }
            else
            {
                // invalid format
                return false;
            }
        }
        /// <summary>
        /// Get char from a version number
        /// </summary>
        /// <param name="versionNumber"></param>
        /// <returns></returns>
        private static byte GetVersionChar(int versionNumber)
        {
            if (versionNumber < 0 || versionNumber > 35)
                throw new ArgumentOutOfRangeException("Version number must smaller than 36 and larger than or equal to 0");
            if (versionNumber <= 9)
            {
                return (byte)((int)'0' + versionNumber);
            }
            else
            {
                return (byte)((int)'A' + versionNumber - 10);
            }
        }
        /// <summary>
        /// Get version number from version char
        /// 0 will be returned if error
        /// </summary>
        /// <param name="versionChar"></param>
        /// <returns></returns>
        private static int GetVersionNumber(byte versionChar)
        {
            if (versionChar >= (byte)'0' && versionChar <= (byte)'9')
            {
                return versionChar - '0';
            }
            else if (versionChar >= (byte)'A' && versionChar <= (byte)'Z')
            {
                return versionChar - 'A' + 10;
            }
            else if (versionChar >= (byte)'a' && versionChar <= (byte)'z')
            {
                return versionChar - 'a' + 10;
            }
            else
            {
                return 0;
            }
        }
    }
}
